﻿//
// Copyright (c) Ping Castle. All rights reserved.
// https://www.pingcastle.com
//
// Licensed under the Non-Profit OSL. See LICENSE file in the project root for full license information.
//
using PingCastle.template;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using PingCastle.Rules;
using PingCastle.Healthcheck;

namespace PingCastle.Report
{
	public class ReportHealthCheckSingle : ReportRiskControls<HealthcheckData>, IPingCastleReportUser<HealthcheckData>
    {

        protected HealthcheckData Report;
        public static int MaxNumberUsersInHtmlReport = 100;
		private ADHealthCheckingLicense _license;
		protected Version version;

		public string GenerateReportFile(HealthcheckData report, ADHealthCheckingLicense license, string filename)
		{
			Report = report;
			_license = license;
			version = new Version(Report.EngineVersion.Split(' ')[0]);
			return GenerateReportFile(filename);
		}

		public string GenerateRawContent(HealthcheckData report)
		{
			Report = report;
			_license = null;
			version = new Version(Report.EngineVersion.Split(' ')[0]);
			sb.Length = 0;
			GenerateContent();
			return sb.ToString();
		}

		protected override void GenerateTitleInformation()
		{
			AddEncoded(Report.DomainFQDN);
			Add(" PingCastle ");
			Add(Report.GenerationDate.ToString("yyyy-MM-dd"));
		}


		protected override void GenerateHeaderInformation()
		{
			AddBeginStyle();
			AddLine(TemplateManager.LoadDatatableCss());
			AddLine(GetStyleSheetTheme());
			AddLine(GetRiskControlStyleSheet());
			AddLine(@"</style>");
		}

		protected override void GenerateBodyInformation()
        {
			GenerateNavigation("HealthCheck report", Report.DomainFQDN, Report.GenerationDate);
			GenerateAbout(@"<p><strong>Generated by <a href=""https://www.pingcastle.com"">Ping Castle</a> all rights reserved</strong></p>
<p>Open source components:</p>
<ul>
<li><a href=""https://getbootstrap.com/"">Bootstrap</a> licensed under the <a href=""https://tldrlegal.com/license/mit-license"">MIT license</a></li>
<li><a href=""https://datatables.net/"">DataTables</a> licensed under the <a href=""https://tldrlegal.com/license/mit-license"">MIT license</a></li>
<li><a href=""https://popper.js.org/"">Popper.js</a> licensed under the <a href=""https://tldrlegal.com/license/mit-license"">MIT license</a></li>
<li><a href=""https://jquery.org"">JQuery</a> licensed under the <a href=""https://tldrlegal.com/license/mit-license"">MIT license</a></li>
</ul>");

            Add(@"
<div id=""wrapper"" class=""container well"">
	<noscript>
		<div class=""alert alert-warning"">
			<p>PingCastle reports work best with Javascript enabled.</p>
		</div>
	</noscript>
<div class=""row""><div class=""col-lg-12""><h1>");
            Add(Report.DomainFQDN);
            Add(@" - Healthcheck analysis</h1>
			<h3>Date: ");
            Add(Report.GenerationDate.ToString("yyyy-MM-dd"));
            Add(@" - Engine version: ");
            Add(Report.EngineVersion);
            Add(@"</h3>
");
			Add(@"<div class=""alert alert-info"">
This report has been generated with the ");
			Add(String.IsNullOrEmpty(_license.Edition) ? "Basic" : _license.Edition);
			Add(@" Edition of PingCastle.");
			if (String.IsNullOrEmpty(_license.Edition))
			{
				Add(@"
<br><strong>Being part of a commercial package is forbidden</strong> (selling the information contained in the report).<br>
If you are an auditor, you MUST purchase an Auditor license to share the development effort.
");
			}
			Add(@"</div>
");
			Add(@"</div></div>
");
            GenerateContent();
            Add(@"
</div>
");
        }

		void GenerateContent()
		{
			GenerateSection("Active Directory Indicators", () =>
			{
				GenerateIndicators(Report, Report.AllRiskRules);
				GenerateRiskModelPanel(Report.RiskRules);
			});

			List<RuleBase<HealthcheckData>> applicableRules = GenerateListOfApplicableRules();

			GenerateSection("Stale Objects", () =>
			{
				GenerateSubIndicator("Stale Objects", Report.GlobalScore, Report.StaleObjectsScore, "It is about operations related to user or computer objects");
				GenerateIndicatorPanel("DetailStale", "Stale Objects rule details", RiskRuleCategory.StaleObjects, Report.RiskRules, applicableRules);
			});
			GenerateSection("Privileged Accounts", () =>
			{
				GenerateSubIndicator("Privileged Accounts", Report.GlobalScore, Report.PrivilegiedGroupScore, "It is about administrators of the Active Directory");
				GenerateIndicatorPanel("DetailPrivileged", "Privileged Accounts rule details", RiskRuleCategory.PrivilegedAccounts, Report.RiskRules, applicableRules);
			});
			GenerateSection("Trusts", () =>
			{
				GenerateSubIndicator("Trusts", Report.GlobalScore, Report.TrustScore, "It is about operations related to user or computer objects");
				GenerateIndicatorPanel("DetailTrusts", "Trusts rule details", RiskRuleCategory.Trusts, Report.RiskRules, applicableRules);
			});
			GenerateSection("Anomalies analysis", () =>
			{
				GenerateSubIndicator("Anomalies", Report.GlobalScore, Report.AnomalyScore, "It is about specific security control points");
				GenerateIndicatorPanel("DetailAnomalies", "Anomalies rule details", RiskRuleCategory.Anomalies, Report.RiskRules, applicableRules);
			});
			GenerateSection("Domain Information", GenerateDomainInformation);
			GenerateSection("User Information", GenerateUserInformation);
			GenerateSection("Computer Information", GenerateComputerInformation);
			GenerateSection("Admin Groups", GenerateAdminGroupsInformation);
			GenerateSection("Trusts", GenerateTrustInformation);
			GenerateSection("Anomalies", GenerateAnomalyDetail);
			GenerateSection("Password Policies", GeneratePasswordPoliciesDetail);
			GenerateSection("GPO", GenerateGPODetail);
		}

		protected List<RuleBase<HealthcheckData>> GenerateListOfApplicableRules()
		{
			var applicableRules = new List<RuleBase<HealthcheckData>>();
			foreach (var rule in RuleSet<HealthcheckData>.Rules)
			{
				object[] models = rule.GetType().GetCustomAttributes(typeof(RuleIntroducedInAttribute), true);
				if (models != null && models.Length != 0)
				{
					RuleIntroducedInAttribute model = (RuleIntroducedInAttribute)models[0];
					if (model.Version <= version)
					{
						applicableRules.Add(rule);
					}
				}
				else
				{
					applicableRules.Add(rule);
				}
			}

			return applicableRules;
		}

		protected override void GenerateFooterInformation()
		{
			AddBeginScript();
			AddLine(TemplateManager.LoadJqueryDatatableJs());
			AddLine(TemplateManager.LoadDatatableJs());
			Add(@"

$(function() {
      $(window).scroll(function() {
         if($(window).scrollTop() >= 70) {  
            $('.information-bar').removeClass('hidden');
            $('.information-bar').fadeIn('fast');
         }else{
            $('.information-bar').fadeOut('fast');
         }
      });
   });
$(document).ready(function(){
	$('table').not('.model_table').DataTable(
		{
			'paging': false,
			'searching': false
		}
	);
	$('[data-toggle=""tooltip""]').tooltip({html: true, container: 'body'});
	$('[data-toggle=""popover""]').popover();

	$('.div_model').on('click', function (e) {
		$('.div_model').not(this).popover('hide');
	});


});
</script>
");
		}

        
        

        
        

        #region domain info
        protected void GenerateDomainInformation()
        {
			bool checkRecycleBin = version >= new Version(2, 7, 0, 0);
			
            Add(@"
		<a name=""domaininformation""></a>
		<div class=""row"">
			<div class=""col-md-12 table-responsive"">
				<table class=""table table-striped table-bordered"">
					<thead>
					<tr> 
						<th>Domain</th>
						<th>Netbios Name</th>
						<th>Domain Functional Level</th>
						<th>Forest Functional Level</th>
						<th>Creation date</th>
						<th>DC count</th>
						<th>Schema version</th>
");
			if (checkRecycleBin)
			{
				Add(@"<th>Recycle Bin enabled</th>
");
			}
			Add(@"</tr>
					</thead>
					<tbody>
					<tr>
						<td class='text'>");
            Add(Report.DomainFQDN);
            Add(@"</td>
						<td class='text'>");
            Add(Report.NetBIOSName);
            Add(@"</td>
						<td class='text'>");
			Add(ReportHelper.DecodeDomainFunctionalLevel(Report.DomainFunctionalLevel));
            Add(@"</td>
						<td class='text'>");
			Add(ReportHelper.DecodeForestFunctionalLevel(Report.ForestFunctionalLevel));
            Add(@"</td>
						<td class='text'>");
            Add(Report.DomainCreation);
            Add(@"</td>
						<td class='num'>");
            Add(Report.NumberOfDC);
			Add(@"</td>
						<td class='text'>");
			Add(ReportHelper.GetSchemaVersion(Report.SchemaVersion));
            if (checkRecycleBin)
			{
				Add(@"</td>
						<td class='text'>");
				if (Report.IsRecycleBinEnabled)
				{
					Add("TRUE");
				}
				else
				{
					Add("<span class=\"unticked\">FALSE</span>");
				}
			}
			Add(@"</td>
					</tr>
					</tbody>
					<tfoot></tfoot>
				</table>
			</div>
		</div>
");
        }

        #endregion domain info

        #region user info
		
		void AddAccountCheckHeader(bool computerView)
		{
			Add(@"<th>Nb Enabled&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of accounts not set as disabled."">?</i></th>");
			Add(@"<th>Nb Disabled&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of accounts set as disabled."">?</i></th>");
			Add(@"<th>Nb Active&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts where at least one logon occured in the last 6 months."">?</i></th>");
			Add(@"<th>Nb Inactive&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts without any logon during the last 6 months."">?</i></th>");
			if (!computerView)
			{
				Add(@"<th>Nb Locked&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts set as locked."">?</i></th>");
				Add(@"<th>Nb pwd never Expire&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts which have a password which never expires."">?</i></th>");
			}
			Add(@"<th>Nb SidHistory&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts having the attribute SIDHistory set. This attributes indicates a foreign origin."">?</i></th>");
			Add(@"<th>Nb Bad PrimaryGroup&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled account whose the group set as primary is not the default one."">?</i></th>");
			if (!computerView)
			{
				Add(@"<th>Nb Password not Req.&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts which have a flag set in useraccountcontrol allowing empty passwords."">?</i></th>");
				Add(@"<th>Nb Des enabled.&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts allowing the unsafe DES algorithm for authentication."">?</i></th>");
			}
			Add(@"<th>Nb unconstrained delegations&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts having been granted the right to impersonate any users without any restrictions."">?</i></th>");
			Add(@"<th>Nb Reversible password&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the number of enabled accounts whose password can be retrieved in clear text using hacking tools."">?</i></th>");
		}

		protected void GenerateUserInformation()
        {
			GenerateSubSection("Account analysis", "useraccountanalysis");
            Add(@"
		<div class=""row"">
			<div class=""col-md-12 table-responsive"">
				<table class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Nb User Accounts</th>
");
			AddAccountCheckHeader(false);
			Add(@"
				</tr>
				</thead>
				<tbody>
					<tr>
						<td class='num'>");
            Add(Report.UserAccountData.Number);
            Add(@"</td><td class='num'>");
            Add(Report.UserAccountData.NumberEnabled);
            Add(@"</td><td class='num'>");
            Add(Report.UserAccountData.NumberDisabled);
            Add(@"</td><td class='num'>");
            Add(Report.UserAccountData.NumberActive);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectioninactiveuser", Report.UserAccountData.NumberInactive, Report.UserAccountData.ListInactive);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectionlockeduser", Report.UserAccountData.NumberLocked, Report.UserAccountData.ListLocked);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectionneverexpiresuser", Report.UserAccountData.NumberPwdNeverExpires, Report.UserAccountData.ListPwdNeverExpires);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectionsidhistoryuser", Report.UserAccountData.NumberSidHistory, Report.UserAccountData.ListSidHistory);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectionbadprimarygroupuser", Report.UserAccountData.NumberBadPrimaryGroup, Report.UserAccountData.ListBadPrimaryGroup);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectionpwdnotrequireduser", Report.UserAccountData.NumberPwdNotRequired, Report.UserAccountData.ListPwdNotRequired);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectiondesenableduser", Report.UserAccountData.NumberDesEnabled, Report.UserAccountData.ListDesEnabled);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectiontrusteddelegationuser", Report.UserAccountData.NumberTrustedToAuthenticateForDelegation, Report.UserAccountData.ListTrustedToAuthenticateForDelegation);
            Add(@"</td><td class='num'>");
            SectionList("usersaccordion", "sectionreversiblenuser", Report.UserAccountData.NumberReversibleEncryption, Report.UserAccountData.ListReversibleEncryption);
            Add(@"</td>
                    </tr>
				</tbody>
				</table>
			</div>
		</div>
");
			GenerateListAccount(Report.UserAccountData, "user", "usersaccordion");
			GenerateDomainSIDHistoryList(Report.UserAccountData);
        }

		private void GenerateListAccount(HealthcheckAccountData data, string root, string accordion)
		{
			GenerateAccordion(accordion, 
                () =>
                {
                    if (data.ListInactive != null && data.ListInactive.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectioninactive" + root, "Inactive objects (Last usage > 6 months) ", data.ListInactive);
                    }
                    if (data.ListLocked != null && data.ListLocked.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionlocked" + root, "Locked objects ", data.ListLocked);
                    }
                    if (data.ListPwdNeverExpires != null && data.ListPwdNeverExpires.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionneverexpires" + root, "Objects with a password which never expires ", data.ListPwdNeverExpires);
                    }
                    if (data.ListSidHistory != null && data.ListSidHistory.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionsidhistory" + root, "Objects having the SIDHistory populated ", data.ListSidHistory);
                    }
                    if (data.ListBadPrimaryGroup != null && data.ListBadPrimaryGroup.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionbadprimarygroup" + root, "Objects having the primary group attribute changed ", data.ListBadPrimaryGroup);
                    }
                    if (data.ListPwdNotRequired != null && data.ListPwdNotRequired.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionpwdnotrequired" + root, "Objects which can have an empty password ", data.ListPwdNotRequired);
                    }
                    if (data.ListDesEnabled != null && data.ListDesEnabled.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectiondesenabled" + root, "Objects which can use DES in kerberos authentication ", data.ListDesEnabled);
                    }
                    if (data.ListTrustedToAuthenticateForDelegation != null && data.ListTrustedToAuthenticateForDelegation.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectiontrusteddelegation" + root, "Objects trusted to authenticate for delegation ", data.ListTrustedToAuthenticateForDelegation);
                    }
                    if (data.ListReversibleEncryption != null && data.ListReversibleEncryption.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionreversible" + root, "Objects having a reversible password ", data.ListReversibleEncryption);
                    }
                    if (data.ListDuplicate != null && data.ListDuplicate.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionduplicate" + root, "Objects being duplicates ", data.ListDuplicate);
                    }
                    if (data.ListNoPreAuth != null && data.ListNoPreAuth.Count > 0)
                    {
                        GenerateListAccountDetail(accordion, "sectionnopreauth" + root, "Objects without kerberos preauthentication ", data.ListNoPreAuth);
                    }
                });
		}

		void SectionList(string accordion, string section, int value, List<HealthcheckAccountDetailData> list)
		{
            if (value > 0 && list != null && list.Count > 0)
            {
                Add(@"<a data-toggle=""collapse"" href=""#");
                Add(section);
                Add(@""" data-parent=""#");
                Add(accordion);
                Add(@""">");
                Add(value);
                Add(@"</a>");
            }
            else
            {
                Add(value);
            }
		}

		void GenerateListAccountDetail(string accordion, string id, string title, List<HealthcheckAccountDetailData> list)
		{
			if (list == null)
			{
				return;
			}
			GenerateAccordionDetail(id, accordion, title, list.Count, false, () =>
				{
					Add(@"
								<div class=""col-md-12 table-responsive"">
									<table class=""table table-striped table-bordered"">
									<thead>
									<tr> 
										<th>Name</th>
										<th>Creation</th>
										<th>Last logon</th>
										<th>Distinguished name</th>
									</tr>
									</thead>
									<tbody>");
					int number = 0;
					list.Sort((HealthcheckAccountDetailData a, HealthcheckAccountDetailData b)
						=>
						{
							return String.Compare(a.Name, b.Name);
						}
						);
					foreach (HealthcheckAccountDetailData detail in list)
					{
						Add(@"
										<tr><td class='text'>");
						AddEncoded(detail.Name);
						Add(@"</td>
											<td class='text'>");
						Add((detail.CreationDate > DateTime.MinValue ? detail.CreationDate.ToString("u") : "Access Denied"));
						Add(@"</td>
											<td class='text'>");
						Add((detail.LastLogonDate > DateTime.MinValue ? detail.LastLogonDate.ToString("u") : "Never"));
						Add(@"</td>
											<td class='text'>");
						AddEncoded(detail.DistinguishedName);
						Add(@"</td>
										</tr>");
						number++;
						if (number >= MaxNumberUsersInHtmlReport)
						{
							break;
						}
					}
					Add(@"
									</tbody>");
					if (number >= MaxNumberUsersInHtmlReport)
					{
						Add("<tfoot><tr><td colspan='4' class='text'>Output limited to ");
						Add(MaxNumberUsersInHtmlReport);
						Add(" items - go to the advanced menu before running the report or add \"--no-enum-limit\" to remove that limit</td></tr></tfoot>");
					}				
					Add(@"
									</table>
								</div>
");
				});
		}

		private void GenerateDomainSIDHistoryList(HealthcheckAccountData data)
		{
			if (data.ListDomainSidHistory == null || data.ListDomainSidHistory.Count == 0)
				return;

            GenerateSubSection("SID History", "sidhistory");
            Add(@"
		<div class=""row"">
			<div class=""col-md-12 table-responsive"">
				<table class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>SID History from domain</th>
					<th>First date seen&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the youngest creation date of an object having SIDHistory related to this domain"">?</i></th>
					<th>Last date seen&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the oldest creation date of an object having SIDHistory related to this domain"">?</i></th>
					<th>Count</th>
				</tr></thead>
				<tbody>");
			data.ListDomainSidHistory.Sort(
				(HealthcheckSIDHistoryData x, HealthcheckSIDHistoryData y) =>
				{
					return String.Compare(x.FriendlyName, y.FriendlyName);
				}
				);
			foreach (HealthcheckSIDHistoryData domainSidHistory in data.ListDomainSidHistory)
			{
                Add("<tr><td class='text'>");
                AddEncoded(domainSidHistory.FriendlyName);
                Add("</td><td class='text'>");
                Add(domainSidHistory.FirstDate);
                Add("</td><td class='text'>");
                Add(domainSidHistory.LastDate);
                Add("</td><td class='num'>");
                Add(domainSidHistory.Count);
                Add("</td></tr>");
			}
			Add(@"
				</tbody></table>
			</div>
		</div>");
		}

		#endregion user info
		#region computer info
		protected void GenerateComputerInformation()
		{
			GenerateSubSection("Account analysis", "computeraccountanalysis");
			Add(@"
		<div class=""row"">
			<div class=""col-md-12 table-responsive"">
				<table class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Nb Computer Accounts</th>
");
			AddAccountCheckHeader(true);
			Add(@"
				</tr>
				</thead>
				<tbody>
					<tr>
						<td class='num'>");
            Add(Report.ComputerAccountData.Number);
            Add(@"</td>
						<td class='num'>");
            Add(Report.ComputerAccountData.NumberEnabled);
            Add(@"</td>
						<td class='num'>");
            Add(Report.ComputerAccountData.NumberDisabled);
            Add(@"</td>
						<td class='num'>");
            Add(Report.ComputerAccountData.NumberActive);
            Add(@"</td>
						<td class='num'>");
            SectionList("computersaccordion", "sectioninactivecomputer", Report.ComputerAccountData.NumberInactive, Report.ComputerAccountData.ListInactive);
            Add(@"</td><td class='num'>");
            SectionList("computersaccordion", "sectionsidhistorycomputer", Report.ComputerAccountData.NumberSidHistory, Report.ComputerAccountData.ListSidHistory);
            Add(@"</td><td class='num'>");
            SectionList("computersaccordion", "sectionbadprimarygroupcomputer", Report.ComputerAccountData.NumberBadPrimaryGroup, Report.ComputerAccountData.ListBadPrimaryGroup);
            Add(@"</td><td class='num'>");
            SectionList("computersaccordion", "sectiontrusteddelegationcomputer", Report.ComputerAccountData.NumberTrustedToAuthenticateForDelegation, Report.ComputerAccountData.ListTrustedToAuthenticateForDelegation);
            Add(@"</td><td class='num'>");
            SectionList("computersaccordion", "sectionreversiblencomputer", Report.ComputerAccountData.NumberReversibleEncryption, Report.ComputerAccountData.ListReversibleEncryption);
            Add(@"</td></tr>
				</tbody>
				</table>
			</div>
		</div>
");
			GenerateListAccount(Report.ComputerAccountData, "computer", "computersaccordion");
			GenerateOperatingSystemList();
			GenerateDomainSIDHistoryList(Report.ComputerAccountData);
			GenerateDCInformation();
		}

		private void GenerateOperatingSystemList()
		{
            GenerateSubSection("Operating Systems", "operatingsystems");
			bool oldOS = version <= new Version(2, 5, 0, 0);
			if (oldOS)
			{
				Add(@"
			<div class=""row"">
				<div class=""col-md-12 table-responsive"">
					<table class=""table table-striped table-bordered"">
					<thead><tr> 
						<th>Operating System</th>
						<th>Count</th>
					</tr></thead>
					<tbody>");
				Report.OperatingSystem.Sort(
					(HealthcheckOSData x, HealthcheckOSData y) =>
					{
						return OrderOS(x.OperatingSystem, y.OperatingSystem);
					}
					);
				{
					foreach (HealthcheckOSData os in Report.OperatingSystem)
					{
						Add("<tr><td class='text'>");
						AddEncoded(os.OperatingSystem);
						Add("</td><td class='num'>");
						Add(os.NumberOfOccurence);
						Add("</td></tr>");
					}
				}
				Add(@"
					</tbody></table>
				</div>
			</div>");
			}
			else
			{
				Add(@"
			<div class=""row"">
				<div class=""col-md-12 table-responsive"">
					<table class=""table table-striped table-bordered"">
					<thead><tr> 
						<th>Operating System</th>
						<th>Nb OS</th>
						");
				AddAccountCheckHeader(true);
				Add(@"
					</tr></thead>
					<tbody>");
				Report.OperatingSystem.Sort(
					(HealthcheckOSData x, HealthcheckOSData y) =>
					{
						return OrderOS(x.OperatingSystem, y.OperatingSystem);
					}
					);
				{
					foreach (HealthcheckOSData os in Report.OperatingSystem)
					{
						Add(@"<tr>
						<td>");
						Add(os.OperatingSystem);
						Add(@"</td>
									<td class='num'>");
						Add(os.data.Number);
						Add(@"</td>
									<td class='num'>");
						Add(os.data.NumberEnabled);
						Add(@"</td>
									<td class='num'>");
						Add(os.data.NumberDisabled);
						Add(@"</td>
									<td class='num'>");
						Add(os.data.NumberActive);
						Add(@"</td>
									<td class='num'>");
						Add(os.data.NumberInactive);
						Add(@"</td><td class='num'>");
						Add(os.data.NumberSidHistory);
						Add(@"</td><td class='num'>");
						Add(os.data.NumberBadPrimaryGroup);
						Add(@"</td><td class='num'>");
						Add(os.data.NumberTrustedToAuthenticateForDelegation);
						Add(@"</td><td class='num'>");
						Add(os.data.NumberReversibleEncryption);
						Add(@"</td></tr>");
					}
				}
				Add(@"
					</tbody></table>
				</div>
			</div>");
			}
		}

		private void GenerateDCInformation()
		{
			if (Report.DomainControllers == null || Report.DomainControllers.Count == 0)
				return;

            GenerateSubSection("Domain controllers", "domaincontrollersection");
			Add(@"
		<div class=""row col-lg-12"">
			<p>Here is a specific zoom related to the Active Directory servers: the domain controllers.</p>
		</div>
");
			GenerateAccordion("domaincontrollers", ()
				=>
				{
					GenerateAccordionDetail("domaincontrollersdetail", "domaincontrollers", "Domain controllers", Report.DomainControllers.Count, false,
						() =>
						{
							Add(@"
				<div class=""col-md-12 table-responsive"">
					<table class=""table table-striped table-bordered"">
					<thead>
					<tr> 
						<th>Domain controller</th>
						<th>Operating System</th>
						<th>Creation Date&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the creation date of the underlying computer object."">?</i></th>
						<th>Startup Time</th>
						<th>Uptime</th>
						<th>Owner&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the owner of the underlying domain controller object stored in the active directory partition. The nTSecurityDescriptor attribute stores its value."">?</i></th>
						<th>Null sessions&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if an anonymous user can extract information from the domain controller"">?</i></th>
						<th>SMB v1&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if the domain controller supports this unsafe SMB v1 network protocol."">?</i></th>
");
							if (version >= new Version(2, 5, 3))
							{
								Add(@"<th>Remote spooler&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if the spooler service is remotely accessible."">?</i></th>");
							}
							if (version >= new Version(2, 7))
							{
								Add(@"<th>FSMO role&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Flexible Single Master Operation. Indicates the server responsible for each role."">?</i></th>");
							}
							Add(@"
									</tr>
									</thead>
									<tbody>");

							int count = 0;
							foreach (var dc in Report.DomainControllers)
							{
								count++;
								Add(@"
<tr><td class='text'>");
								AddEncoded(dc.DCName);
								Add(@"</td>
<td class='text'>");
								AddEncoded(dc.OperatingSystem);
								Add(@"</td>
<td class='text'>");
								Add((dc.CreationDate == DateTime.MinValue ? "Unknown" : dc.CreationDate.ToString("u")));
								Add(@"</td>
<td class='text'>");
								Add((dc.StartupTime == DateTime.MinValue ? (dc.LastComputerLogonDate.AddDays(60) < DateTime.Now ? "Inactive?" : "Unknown") : (dc.StartupTime.AddMonths(6) < DateTime.Now ? "<span class='unticked'>" + dc.StartupTime.ToString("u") + "</span>" : dc.StartupTime.ToString("u"))));
								Add(@"</td>
<td class='text'>");
								Add((dc.StartupTime == DateTime.MinValue ? "" : (DateTime.Now.Subtract(dc.StartupTime)).Days + " days"));
								Add(@"</td>
<td class='text'>");
								Add((String.IsNullOrEmpty(dc.OwnerName) ? dc.OwnerSID : dc.OwnerName));
								Add(@"</td>
<td class='text'>");
								Add((dc.HasNullSession ? "<span class='unticked'>YES</span>" : "<span class='ticked'>NO</span>"));
								Add(@"</td>
<td class='text'>");
								Add((dc.SupportSMB1 ? "<span class='unticked'>YES</span>" : "<span class='ticked'>NO</span>"));
								Add(@"</td>
");
								if (version >= new Version(2, 5, 3))
								{
									Add(@"<Td>");
									Add((dc.RemoteSpoolerDetected ? "<span class='unticked'>YES</span>" : "<span class='ticked'>NO</span>"));
									Add("</Td>");
								}
								if (version >= new Version(2, 7))
								{
									Add(@"<Td>");
									if (dc.FSMO != null)
									{
										Add(string.Join(",<br>", dc.FSMO.ConvertAll(x => ReportHelper.Encode(x)).ToArray()));
									}
									Add("</Td>");
								}
								Add(@"</tr>
");
							}
							Add(@"
					</tbody>
					</table>
				</div>
");
						}
					);
				}
			);

		}


		#endregion computer info

		#region admin groups
		protected void GenerateAdminGroupsInformation()
		{
			if (Report.PrivilegedGroups != null)
			{
				GenerateSubSection("Groups", "admingroups");
				Add(@"
		<div class=""row col-lg-12"">
			<p>This section is focused on the groups which are critical for admin activities. If the report has been saved which the full details, each group can be zoomed with its members. If it is not the case, for privacy reasons, only general statictics are available.</p>
		</div>
");
				Add(@"
		<div class=""row"">
			<div class=""col-md-12 table-responsive mb-2"">
				<table class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Group Name</th>
					<th>Nb Admins&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of user accounts member of this group"">?</i></th>
					<th>Nb Enabled&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of user accounts not marked as disabled"">?</i></th>
					<th>Nb Disabled&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of user accounts marked as disabled"">?</i></th>
					<th>Nb Inactive&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of enabled user accounts without login activities far at least 6 months"">?</i></th>
					<th>Nb PWd never expire&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of enabled user accounts having a password marked as never expire"">?</i></th>
");
				if (version >= new Version(2, 5, 2))
				{
					Add(@"<th>Nb Smart Card required&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of enabled user accounts required to have a smart card"">?</i></th>");
				}
				if (version >= new Version(2, 5, 3))
				{
					Add(@"<th>Nb Service accounts&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of enabled user accounts authorized to be a service. This is defined by setting the attribute servicePrincipalName."">?</i></th>");
				}
				Add(@"
					<th>Nb can be delegated&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of enabled user accounts which doesn't have the flag 'this account is sensitive and cannot be delegated'. This is an effective mitigation against unconstrained delegation attacks."">?</i></th>
					<th>Nb external users&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""This is the number of item identified as coming from a foreign domain"">?</i></th>
				</tr>
				</thead>
				<tbody>
");
				Report.PrivilegedGroups.Sort((HealthCheckGroupData a, HealthCheckGroupData b)
					=>
					{
						return String.Compare(a.GroupName, b.GroupName);
					}
				);
				foreach (HealthCheckGroupData group in Report.PrivilegedGroups)
				{
                    Add(@"
				<tr>
					<td class='text'>");
					if (group.Members != null && group.Members.Count > 0)
					{
						Add(@"<a data-toggle=""modal"" href=""#");
						Add(GenerateModalAdminGroupIdFromGroupName(group.GroupName));
						Add(@""">");
						AddEncoded(group.GroupName);
						Add("</a>");
					}
					else
					{
						AddEncoded(group.GroupName);
					}
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfMember);
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfMemberEnabled);
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfMemberDisabled);
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfMemberInactive);
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfMemberPwdNeverExpires);
					if (version >= new Version(2, 5, 2))
					{
						Add(@"</td>
					<td class='text'>");
						Add(group.NumberOfSmartCardRequired);
					}
					if (version >= new Version(2, 5, 3))
					{
						Add(@"</td>
					<td class='text'>");
						Add(group.NumberOfServiceAccount);
					}
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfMemberCanBeDelegated);
                    Add(@"</td>
					<td class='text'>");
                    Add(group.NumberOfExternalMember);
                    Add(@"</td>
				</tr>
	");
				}
            Add(@"
				</tbody>
				</table>
			</div>
		</div>
");
				foreach (HealthCheckGroupData group in Report.PrivilegedGroups)
				{
					if (group.Members != null && group.Members.Count > 0)
					{
						GenerateModalAdminGroup(GenerateModalAdminGroupIdFromGroupName(group.GroupName), group.GroupName,
									() => GenerateAdminGroupsDetail(group.Members));
					}
				}
            }

            if (Report.AllPrivilegedMembers != null && Report.AllPrivilegedMembers.Count > 0)
            {
				Add(@"
		<div class=""row"">
			<div class=""col-md-12"">
");
                GenerateAccordion("admingroupsaccordeon",
                    () =>
					{
						GenerateAccordionDetail("allprivileged", "admingroupsaccordeon", "All users in Admins groups", Report.AllPrivilegedMembers.Count, false, () => GenerateAdminGroupsDetail(Report.AllPrivilegedMembers));
					});
				Add("</div></div>");
            }
			if (Report.Delegations != null && Report.Delegations.Count > 0)
			{
				Add(@"
		<div class=""row"">
			<div class=""col-md-12"">
");
				GenerateSubSection("Delegations", "admindelegation");
				Add(@"
		<div class=""row col-lg-12"">
			<p>Each specific rights defined for Organizational Unit (OU) are listed below.</p>
		</div>
");
				GenerateAccordion("delegationaccordeon",
					() =>
					{
						GenerateAccordionDetail("alldelegation", "delegationaccordeon", "All delegations", Report.Delegations.Count, false, GenerateDelegationDetail);
					});
				Add("</div></div>");
			}
		}
		private string GenerateModalAdminGroupIdFromGroupName(string groupname)
		{
			return "modal" + groupname.Replace(" ", "-").Replace("<","");
		}
		private void GenerateModalAdminGroup(string id, string title, GenerateContentDelegate content)
		{
            Add(@"
			<!-- Modal ");
            Add(id);
            Add(@"-->
			<div class=""modal fade"" id=""" + id + @""" role=""dialog"">
				<div class=""modal-dialog modal-xl"">
				<!-- Modal content-->
					<div class=""modal-content"">
						<div class=""modal-header"">
							<h4 class=""modal-title"">" + title + @"</h4>
							<button type=""button"" class=""close"" data-dismiss=""modal"" aria-label=""Close"">
								<span aria-hidden=""true"">&times;</span>
							</button>
						</div>
						<div class=""modal-body"">
");
            content();
            Add(@"
						</div>
						<div class=""modal-footer"">
							<button type=""button"" class=""btn btn-secondary"" data-dismiss=""modal"">Close</button>
						</div>
					</div>
				</div>
			</div>
			<!-- Modal ");
            Add(id);
            Add(@" end -->
");
		}

		private void GenerateDelegationDetail()
		{
			Add(@"
<div class=""row"">
<div class=""col-lg-12 table-responsive"">
<table class=""table table-striped table-bordered"">
<thead><tr> 
<th>DistinguishedName</th>
<th>Account</th>
<th>Right</th>
</tr>
</thead>
<tbody>
");
			Report.Delegations.Sort(OrderDelegationData);

			foreach (HealthcheckDelegationData delegation in Report.Delegations)
			{
				int dcPathPos = delegation.DistinguishedName.IndexOf(",DC=");
				string path = delegation.DistinguishedName;
				if (dcPathPos > 0)
					path = delegation.DistinguishedName.Substring(0, dcPathPos);
                Add(@"<tr>
<td class='text'>");
                AddEncoded(path);
                Add(@"</td>
<td class='text'>");
                AddEncoded(delegation.Account);
                Add(@"</td>
<td class='text'>");
                AddEncoded(delegation.Right);
                Add(@"</td>
</tr>
");
			}
			Add(@"</tbody>
</table>
</div>
</div>
<br>");
		}

		private void GenerateAdminGroupsDetail(List<HealthCheckGroupMemberData> members)
		{
			if (members != null)
			{
				Add(@"
<div class=""row"">
<div class=""col-lg-12"">
<table class=""table table-responsive table-striped table-bordered"">
<thead><tr> 
	<th>SamAccountName&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates login name of the user account."">?</i></th>
	<th>Enabled&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if the account is not marked as disabled."">?</i></th>
	<th>Active&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if the user is not set as disabled and at least one login occured during the last 6 months."">?</i></th>
	<th>Pwd never Expired&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates for enabled accounts if the password is set to never expires."">?</i></th>
	<th>Locked&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates for enabled accounts if the account is locked"">?</i></th>
");
				if (version >= new Version(2, 5, 2))
				{
					Add(@"<th>Smart Card required&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates for enabled accounts if a smart card is required to login"">?</i></th>");
				}
				if (version >= new Version(2, 5, 3))
				{
					Add(@"<th>Service account&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates for enabled accounts it has been marked as service. This is done by setting the servicePrincipalName attribute."">?</i></th>");
				}
				Add(@"<th>Flag Cannot be delegated present&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates for enabled accounts if the protection 'this is account is sensitive and cannot be delegated' is in place."">?</i></th>
	<th>Distinguished name&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates the location of the object in the AD tree."">?</i></th>
</tr>
</thead>
<tbody>
");
				members.Sort((HealthCheckGroupMemberData a,HealthCheckGroupMemberData b)
					=>
						{
							return String.Compare(a.Name, b.Name);
						}
				);
				foreach (HealthCheckGroupMemberData member in members)
				{
					if (member.IsExternal)
					{
                        Add(@"<tr>
	<td class='text'>");
                        AddEncoded(member.Name);
                        Add(@"</td>
	<td class='text'>External</td>
	<td class='text'>External</td>
	<td class='text'>External</td>
	<td class='text'>External</td>
	<td class='text'>External</td>
	<td class='text'>");
						if (version >= new Version(2, 5, 2))
						{
							Add(@"External</td>
	<td class='text'>");
						}
						if (version >= new Version(2, 5, 3))
						{
							Add(@"External</td>
	<td class='text'>");
						}
                        AddEncoded(member.DistinguishedName);
                        Add(@"</td>
</tr>
");
					}
					else
					{
                        Add(@"<tr>
	<td class='text'>");
                        AddEncoded(member.Name);
                        Add(@"</td>
	<td class='text'>");
                        Add((member.IsEnabled ? "<span class='ticked'>&#10003;</span>" : "<span class='unticked'>&#10007;</span>"));
                        Add(@"</td>
	<td class='text'>");
                        Add((member.IsActive ? "<span class='ticked'>&#10003;</span>" : "<span class='unticked'>&#10007;</span>"));
                        Add(@"</td>
	<td class='text'>");
                        Add((member.DoesPwdNeverExpires ? "<span class='unticked'>YES</span>" : "<span class='ticked'>NO</span>"));
                        Add(@"</td>
	<td class='text'>");
                        Add((member.IsLocked ? "<span class='unticked'>YES</span>" : "<span class='ticked'>NO</span>"));
                        Add(@"</td>
	<td class='text'>");
						if (version >= new Version(2, 5, 2))
						{
							Add((member.SmartCardRequired ? "<span class='ticked'>YES</span>" : "<span>NO</span>"));
							Add(@"</td>
	<td class='text'>");
						}
						if (version >= new Version(2, 5, 3))
						{
							Add((member.IsService ? "<span class='unticked'>YES</span>" : "<span>NO</span>"));
							Add(@"</td>
	<td class='text'>");
						}
                        Add((!member.CanBeDelegated ? "<span class='ticked'>YES</span>" : "<span class='unticked'>NO</span>"));
                        Add(@"</td>
	<td class='text'>");
                        AddEncoded(member.DistinguishedName);
                        Add(@"</td>
</tr>
");
					}
				}
				Add(@"</tbody>
</table>
</div>
</div>
");
			}
		}

		// revert an OU string order to get a string orderable
		// ex: OU=myOU,DC=DC   => DC=DC,OU=myOU
		private string GetDelegationSortKey(HealthcheckDelegationData a)
		{
			string[] apart = a.DistinguishedName.Split(',');
			string[] apart1 = new string[apart.Length];
			for (int i = 0; i < apart.Length; i++)
			{
				apart1[i] = apart[apart.Length - 1 - i];
			}
			return String.Join(",", apart1);
		}
		private int OrderDelegationData(HealthcheckDelegationData a, HealthcheckDelegationData b)
		{
			if (a.DistinguishedName == b.DistinguishedName)
				return String.Compare(a.Account, b.Account);
			return String.Compare(GetDelegationSortKey(a), GetDelegationSortKey(b));
		}

		#endregion admin groups

		#region trust
		protected void GenerateTrustInformation()
		{
			List<string> knowndomains = new List<string>();
            GenerateSubSection("Discovered Domains", "discovereddomains");
            Add(@"
		<div class=""row"">
			<div class=""col-md-12 table-responsive"">
				<table class=""table table-striped table-bordered"">
					<thead><tr>
					<th>Trust Partner</th>
					<th>Type</th>
					<th>Attribut</th>
					<th>Direction&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""<b>Bidirectional:</b> Each domain or forest has access to the resources of the other domain or forest. <br><b>Inbound:</b> The other domain or forest has access to the resources of this domain or forest. This domain or forest does not have access to resources that belong to the other domain or forest. <br><b>Outbound:</b> This domain or forest has access to resources of the other domain or forest. The other domain or forest does not have access to the resources of this domain or forest."">?</i></th>
					<th>SID Filtering active&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if the protection for the trust has been enabled or disabled."">?</i></th>
					<th>TGT Delegation&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates if the kerberos delegation works accross forest trusts"">?</i></th>
					<th>Creation&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""Indicates creation date of the underlying AD object"">?</i></th>
					<th>Is Active ?&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title=""""  data-original-title=""The account used to store the secret should be modified every 30 days if it is active. It indicates if a change occured during the last 40 days"">?</i></th>
					</tr>
					</thead>
					<tbody>
");
			foreach (HealthCheckTrustData trust in Report.Trusts)
			{
				string sid = (string.IsNullOrEmpty(trust.SID) ? "[Unknown]" : trust.SID);
				string netbios = (string.IsNullOrEmpty(trust.NetBiosName) ? "[Unknown]" : trust.NetBiosName);
				string sidfiltering = TrustAnalyzer.GetSIDFiltering(trust);
				if (sidfiltering == "Yes")
				{
					sidfiltering = "<span class=\"ticked\">" + sidfiltering + "</span>";
				}
				else if (sidfiltering == "No")
				{
					sidfiltering = "<span class=\"unticked\">"+ sidfiltering + "</span>";
				}
				string tgtDelegation = TrustAnalyzer.GetTGTDelegation(trust);
				if (tgtDelegation == "Yes")
				{
					tgtDelegation = "<span class=\"unticked\">" + tgtDelegation + "</span>";
				}
				else if (tgtDelegation == "No")
				{
					tgtDelegation = "<span class=\"ticked\">" + tgtDelegation + "</span>";
				}
                Add(@"<tr>
<td class='text'>");
                if (GetUrlCallback == null)
                {
					AddEncoded(trust.TrustPartner);
                    Add(@" <i class=""info-mark d-print-none"" data-toggle=""tooltip"" data-placement=""right"" data-html=""true"" title=""SID:");
                    Add(sid);
                    Add(@"<br>Netbios: ");
                    Add(netbios);
                    Add(@""">");
                    Add(@"?</i>");
                }
                else
                {
                    Add(GetUrlCallback(trust.Domain, trust.TrustPartner));
                }
                Add(@"</td>
<td class='text'>");
                Add(TrustAnalyzer.GetTrustType(trust.TrustType));
                Add(@"</td>
<td class='text'>");
                Add(TrustAnalyzer.GetTrustAttribute(trust.TrustAttributes));
                Add(@"</td>
<td class='text'>");
                Add(TrustAnalyzer.GetTrustDirection(trust.TrustDirection));
                Add(@"</td>
<td class='text'>");
                Add(sidfiltering);
                Add(@"</td>
<td class='text'>");
				Add(tgtDelegation);
				Add(@"</td>
<td class='text'>");
                Add(trust.CreationDate);
                Add(@"</td>
<td class='text'>");
                Add((trust.IsActive ? trust.IsActive.ToString() : "<span class=\"unticked\">False</span>"));
                Add(@"</td>
</tr>
");
			}
			Add(@"
					</tbody>
				</table>
			</div>
		</div>
");

            GenerateSubSection("Reachable Domains");
			Add(@"
		<div class=""row col-lg-12"">
			<p>These are the domains that PingCastle was able to detect but which is not releated to direct trusts. It may be children of a forest or bastions.</p>
		</div>
");
            Add(@"
		<div class=""row"">
			<div class=""col-md-12 table-responsive"">
				<table class=""table table-striped table-bordered"">
					<thead><tr> 
						<th>Reachable domain</th>
						<th>Via</th>
						<th>Netbios</th>
						<th>Creation date</th>
						</tr>
					</thead>
					<tbody>
");
			foreach (HealthCheckTrustData trust in Report.Trusts)
			{
				if (trust.KnownDomains == null)
					continue;
				trust.KnownDomains.Sort((HealthCheckTrustDomainInfoData a, HealthCheckTrustDomainInfoData b)
					=>
				{
					return String.Compare(a.DnsName, b.DnsName);
				}
				);
				foreach (HealthCheckTrustDomainInfoData di in trust.KnownDomains)
				{
                    Add(@"<tr>
<td class='text'>");
                    if (GetUrlCallback == null)
                    {
                        AddEncoded(di.DnsName);
                    }
                    else
                    {
                        Add(GetUrlCallback(di.Domain, di.DnsName));
                    }
                Add(@"</td>
<td class='text'>");
                    if (GetUrlCallback == null)
                    {
                        AddEncoded(trust.TrustPartner);
                    }
                    else
                    {
                        Add(GetUrlCallback(trust.Domain, trust.TrustPartner));
                    }
                Add(@"</td>
<td class='text'>");
                    AddEncoded(di.NetbiosName);
                    Add(@"</td>
<td class='text'>");
					if (di.CreationDate == DateTime.MinValue)
					{
						Add("Unknown");
					}
					else
					{
						Add(di.CreationDate);
					}
                    Add(@"</td>
</tr>
");
				}
			}
			if (Report.ReachableDomains != null)
			{
				foreach (HealthCheckTrustDomainInfoData di in Report.ReachableDomains)
				{
                    Add(@"<tr>
<td class='text'>");
                    if (GetUrlCallback == null)
                    {
                        AddEncoded(di.DnsName);
                    }
                    else
                    {
                        Add(GetUrlCallback(di.Domain, di.DnsName));
                    }
                    Add(@"</td>
<td class='text'>Unknown</td>
<td class='text'>");
                    AddEncoded(di.NetbiosName);
                    Add(@"</td>
<td class='text'>Unknown</td>
</tr>
");
				}
			}

			Add(@"
					</tbody>
				</table>
			</div>
		</div>
");
		}
		#endregion trust

		#region anomaly
		protected void GenerateAnomalyDetail()
		{
            GenerateSubSection("Backup", "backup");
            Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p>The program checks the last date of the AD backup. This date is computed using the replication metadata of the attribute dsaSignature (<a href=""https://technet.microsoft.com/en-us/library/jj130668(v=ws.10).aspx"">reference</a>).</p>
<p><strong>Last backup date: </strong> " + (Report.LastADBackup == DateTime.MaxValue ? "<span class=\"unticked\">Never</span>" : (Report.LastADBackup == DateTime.MinValue ? "<span class=\"unticked\">Not checked (older version of PingCastle)</span>" : Report.LastADBackup.ToString("u"))) + @"</p>
		</div></div>
");

            GenerateSubSection("LAPS", "laps");
            Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p><a href=""https://support.microsoft.com/en-us/kb/3062591"">LAPS</a> is used to have a unique local administrator password on all workstations / servers of the domain.
Then this password is changed at a fixed interval. The risk is when a local administrator hash is retrieved and used on other workstation in a pass-the-hash attack.</p>
<p>Mitigation: having a process when a new workstation is created or install LAPS and apply it through a GPO</p>
<p><strong>LAPS installation date: </strong> " + (Report.LAPSInstalled == DateTime.MaxValue ? "<span class=\"unticked\">Never</span>" : (Report.LAPSInstalled == DateTime.MinValue ? "<span class=\"unticked\">Not checked (older version of PingCastle)</span>" : Report.LAPSInstalled.ToString("u"))) + @"</p>
		</div></div>
");
            GenerateSubSection("Windows Event Forwarding (WEF)");
            Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p>Windows Event Forwarding is a native mechanism used to collect logs on all workstations / servers of the domain.
Microsoft recommends to <a href=""https://docs.microsoft.com/en-us/windows/threat-protection/use-windows-event-forwarding-to-assist-in-instrusion-detection"">Use Windows Event Forwarding to help with intrusion detection</a>
Here is the list of servers configured for WEF found in GPO</p>
<p><strong>Number of WEF servers configured: </strong> " + (Report.GPOEventForwarding.Count) + @"</p>
		</div></div>
");
			// wef
			if (Report.GPOEventForwarding.Count > 0)
			{
				Add(@"
		<div class=""row"">
			<div class=""col-md-12"">");
				GenerateAccordion("wef", () =>
					{
						GenerateAccordionDetail("wefPanel", "wef", "Windows Event Forwarding servers", Report.GPOEventForwarding.Count, false, () => 
							{
								Add(@"
									<div class=""col-md-12 table-responsive"">
										<TABLE class=""table table-striped table-bordered"">
											<thead><tr> 
												<th>GPO Name</th>
												<th>Order</th>
												<th>Server</th>
												</tr>
											</thead>
											<tbody>
								");
								// descending sort
								Report.GPOEventForwarding.Sort(
									(GPOEventForwardingInfo a, GPOEventForwardingInfo b)
										=>
									{
										int comp = String.Compare(a.GPOName, b.GPOName);
										if (comp == 0)
											comp = (a.Order > b.Order ? 1 : (a.Order == b.Order ? 0 : -1));
										return comp;
									}
									);

								foreach (var info in Report.GPOEventForwarding)
								{
									Add(@"
				<tr>
				<td class='text'>");
									AddEncoded(info.GPOName);
									Add(@"</td>
				<td class='num'>");
									Add(info.Order);
									Add(@"</td>
				<td class='text'>");
									AddEncoded(info.Server);
									Add(@"</td>
				</tr>
				");
								}
								Add(@"
													</tbody></table>
												</div>
				");
							});
					});
				Add(@"
			</div>
		</div>
");
			}


            // krbtgt
            GenerateSubSection("krbtgt (Used for Golden ticket attacks)", "krbtgt");
            Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p>The password of the krbtgt account should be changed twice every 40 days using this <a href=""https://gallery.technet.microsoft.com/Reset-the-krbtgt-account-581a9e51"">script</a></p>
<p>You can use the version gathered using replication metadata from two reports to guess the frequency of the password change or if the two consecutive resets has been done. Version starts at 1.</p>
<p><strong>Kerberos password last changed: </strong> " + Report.KrbtgtLastChangeDate.ToString("u") + @"
<strong>version: </strong> " + Report.KrbtgtLastVersion + @"
</p>
		</div></div>
");
            // adminSDHolder
			GenerateSubSection("AdminSDHolder (detect temporary elevated accounts)", "admincountequalsone");
            Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p>This control detects accounts which are former 'unofficial' admins.
Indeed when an account belongs to a privileged group, the attribute admincount is set. If the attribute is set without being an official member, this is suspicious. To suppress this warning, the attribute admincount of these accounts should be removed after review.</p>
<p><strong>Number of accounts to review:</strong> " +
		(Report.AdminSDHolderNotOKCount > 0 ? "<span class=\"unticked\">" + Report.AdminSDHolderNotOKCount + "</span>" : "0")
	+ @"</p>
		</div></div>
");
			if (Report.AdminSDHolderNotOKCount > 0 && Report.AdminSDHolderNotOK != null && Report.AdminSDHolderNotOK.Count > 0)
			{
				GenerateAccordion("adminsdholder", () => GenerateListAccountDetail("adminsdholder", "adminsdholderpanel", "AdminSDHolder User List", Report.AdminSDHolderNotOK));
			}

			if (Report.DomainControllers != null)
			{
				string nullsession = null;
				int countnullsession = 0;
				foreach (var DC in Report.DomainControllers)
				{
					if (DC.HasNullSession)
					{
						nullsession += @"<tr><td class='text'>" + DC.DCName + @"</td></tr>";
						countnullsession++;
					}
				}
				if (countnullsession > 0)
				{
                    GenerateSubSection("NULL SESSION (anonymous access)", "nullsession");
                    Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p>This control detects domain controllers which can be accessed without authentication.
Hackers can then perform a reconnaissance of the environement with only a network connectivity and no account at all.</p>
			<p><strong>Domain controllers vulnerable:</strong> <span class=""unticked"">" + countnullsession + @"</span>
		</div></div>
		<div class=""row"">
			<div class=""col-md-12"">
");
					GenerateAccordion("nullsessions", () =>
						{
							GenerateAccordionDetail("nullsessionPanel", "nullsessions", "Domain controllers with NULL SESSION Enabled", countnullsession, false, () =>
								{
									Add(@"
								<div class=""col-md-12 table-responsive"">
									<table class=""table table-striped table-bordered"">
									<thead><tr> 
										<th>Domain Controller</th>
									</tr>
									</thead>
									<tbody>
										" + nullsession + @"
									</tbody></table>
								</div>
");								}
							);
						}
					);
					Add(@"
			</div>
		</div>
");
				}

				if (Report.SmartCardNotOK != null && Report.SmartCardNotOK.Count > 0)
				{
                    // smart card
                    GenerateSubSection("Smart Card and Password", "smartcardmandatorywithnopasswordchange");
                    Add(@"
		<div class=""row""><div class=""col-lg-12"">
<p>This control detects users which use only smart card and whose password hash has not been changed for at least 40 days.
Indeed, once the smart card required check is activated in the user account properties, a random password hash is set.
But this hash is not changed anymore like for users having a password whose change is controlled by password policies.
As a consequence, a capture of the hash using a memory attack tool can lead to a compromission of this account unlimited in time.
The best practice is to reset these passwords on a regular basis or to uncheck and check again the &quot;require smart card&quot; property to force a hash change.</p>
			<p><strong>Users with smart card and having their password unchanged since at least 40 days:</strong> " +
        (Report.SmartCardNotOK == null ? 0 : Report.SmartCardNotOK.Count)
        + @"</p>
		</div></div>
");
                    GenerateAccordion("anomalysmartcard", () => GenerateListAccountDetail("anomalysmartcard", "smartcard", "Smart card and Password >40 days List", Report.SmartCardNotOK));
				}

                // logon script
                GenerateSubSection("Logon scripts", "logonscripts");
                Add(@"
		<div class=""row""><div class=""col-lg-12"">
			<p>You can check here backdoors or typo error in the scriptPath attribute</p>
		</div></div>
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Script Name</th>
					<th>Count</th>
					</tr>
				</thead>
				<tbody>
");
				// descending sort
				Report.LoginScript.Sort(
					(HealthcheckLoginScriptData a, HealthcheckLoginScriptData b)
						=>
					{
						return b.NumberOfOccurence.CompareTo(a.NumberOfOccurence);
					}
					);

				int number = 0;
				foreach (HealthcheckLoginScriptData script in Report.LoginScript)
				{
                    Add(@"
<tr>
<td class='text'>");
                    AddEncoded(String.IsNullOrEmpty(script.LoginScript.Trim()) ? "<spaces>" : script.LoginScript);
                    Add(@"</td>
<td class='num'>");
                    Add(script.NumberOfOccurence);
                    Add(@"</td>
</tr>
");
					number++;
					if (number >= MaxNumberUsersInHtmlReport)
					{
						break;
					}
				}
				Add(@"
				</tbody>");
				if (number >= MaxNumberUsersInHtmlReport)
				{
					Add("<tfoot><tr><td colspan='2' class='text'>Output limited to ");
					Add(MaxNumberUsersInHtmlReport);
					Add(" items - go to the advanced menu before running the report or add \"--no-enum-limit\" to remove that limit</td></tr></tfoot>");
				}
				Add(@"
			</table>
		</div>
");
                // certificate
                GenerateSubSection("Certificates", "certificates");
                Add(@"
		<div class=""row"">
			<div class=""col-lg-12"">
				<p>This detects trusted certificate which can be used in man in the middle attacks or which can issue smart card logon certificates</p>
				<p><strong>Number of trusted certificates:</strong> " + Report.TrustedCertificates.Count + @" 
			</div>
		</div>
		<div class=""row"">
			<div class=""col-lg-12"">
");
				GenerateAccordion("trustedCertificates", () =>
					{
						GenerateAccordionDetail("trustedCertificatesPanel", "trustedCertificates", "Trusted certificates", Report.TrustedCertificates.Count, false, () =>
							{
								Add(@"
								<div class=""col-md-12 table-responsive"">
									<table class=""table table-striped table-bordered"">
									<thead><tr> 
										<th>Source</th>
										<th>Store</th>
										<th>Subject</th>
										<th>Issuer</th>
										<th>NotBefore</th>
										<th>NotAfter</th>
										<th>Module size</th>
										<th>Signature Alg</th>
										<th>SC Logon</th>
										</tr>
									</thead>
									<tbody>
");
								foreach (HealthcheckCertificateData data in Report.TrustedCertificates)
								{
									X509Certificate2 cert = new X509Certificate2(data.Certificate);
									bool SCLogonAllowed = false;
									foreach (X509Extension ext in cert.Extensions)
									{
										if (ext.Oid.Value == "1.3.6.1.4.1.311.20.2.2")
										{
											SCLogonAllowed = true;
											break;
										}
									}
									int modulesize = 0;
									RSA key = null;
									try
									{
										key = cert.PublicKey.Key as RSA;
									}
									catch (Exception)
									{
									}
									if (key != null)
									{
										RSAParameters rsaparams = key.ExportParameters(false);
										modulesize = rsaparams.Modulus.Length * 8;
									}
									Add(@"
				<tr>
				<td class='text'>");
									if (data.Source == "NTLMStore")
									{
										Add(@"Enterprise NTAuth&nbsp;<i class=""info-mark d-print-none"" data-placement=""bottom"" data-toggle=""tooltip"" title="""" data-original-title=""This store is used by the Windows PKI. You can view it with the command 'certutil -viewstore -enterprise NTAuth' or edit it with the command 'Manage AD Container' of the 'Enterprise PKI' snapin of mmc."">?</i>");
									}
									else
									{
										AddEncoded(data.Source);
									}
									Add(@"</td>
				<td class='text'>");
									AddEncoded(data.Store);
									Add(@"</td>
				<td class='text text-nowrap'>");
									AddEncoded(cert.Subject);
									Add(@"</td>
				<td class='text text-nowrap'>");
									AddEncoded(cert.Issuer);
									Add(@"</td>
				<td class='text text-nowrap'>");
									Add(cert.NotBefore);
									Add(@"</td>
				<td class='text text-nowrap'>");
									Add(cert.NotAfter);
									Add(@"</td>
				<td class='num'>");
									Add(modulesize);
									Add(@"</td>
				<td class='text'>");
									Add(cert.SignatureAlgorithm.FriendlyName);
									Add(@"</td>
				<td class='text'>");
									Add(SCLogonAllowed);
									Add(@"</td>
				</tr>
				");
								}
								Add(@"
													</tbody></table>
												</div>
				");
							}
						);
					}
					);
				Add(@"
			</div>
		</div>
");
			}
		}
		#endregion anomaly

		#region password policies

		protected void GeneratePasswordPoliciesDetail()
		{
            GenerateSubSection("Password policies", "passwordpolicies");
            Add(@"
		<p>Note: PSO (Password Settings Objects) will be visible only if the user which collected the information has the permission to view it.<br>PSO shown in the report will be prefixed by &quot;PSO:&quot;</p>
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Policy Name</th>
					<th>Complexity</th>
					<th>Max Password Age</th>
					<th>Min Password Age</th>
					<th>Min Password Length</th>
					<th>Password History</th>
					<th>Reversible Encryption</th>
					<th>Lockout Threshold</th>
					<th>Lockout Duration</th>
					<th>Reset account counter locker after</th>
					</tr>
				</thead>
				<tbody>
");
			if (Report.GPPPasswordPolicy != null)
			{
				foreach (GPPSecurityPolicy policy in Report.GPPPasswordPolicy)
				{
                    Add(@"
				<tr>
					<td class='text'>");
                    AddEncoded(policy.GPOName);
                    Add(@"</td>
					<td class='text'>");
                    Add(GetPSOStringValue(policy, "PasswordComplexity"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "MaximumPasswordAge"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "MinimumPasswordAge"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "MinimumPasswordLength"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "PasswordHistorySize"));
                    Add(@"</td>
					<td class='text'>");
                    Add(GetPSOStringValue(policy, "ClearTextPassword"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "LockoutBadCount"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "LockoutDuration"));
                    Add(@"</td>
					<td class='num'>");
                    Add(GetPSOStringValue(policy, "ResetLockoutCount"));
                    Add(@"</td>
				</tr>
");
				}
			}
			Add(@"
				</tbody>
			</table>
		</div>
");
            GenerateSubSection("Screensaver policies");
			Add(@"
		<div class=""row col-lg-12"">
			<p>This is the settings related to screensavers stored in Group Policies. Each non compliant setting is written in red.</p>
		</div>
");
            Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Policy Name</th>
					<th>Screensaver enforced</th>
					<th>Password request</th>
					<th>Start after (seconds)</th>
					<th>Grace Period (seconds)</th>
					</tr>
				</thead>
				<tbody>
");
			if (Report.GPOScreenSaverPolicy != null)
			{
				foreach (GPPSecurityPolicy policy in Report.GPOScreenSaverPolicy)
				{
                    Add(@"
					<tr>
						<td class='text'>");
                    AddEncoded(policy.GPOName);
                    Add(@"</td>
						<td class='num'>");
                    Add(GetPSOStringValue(policy, "ScreenSaveActive"));
                    Add(@"</td>
						<td class='num'>");
                    Add(GetPSOStringValue(policy, "ScreenSaverIsSecure"));
                    Add(@"</td>
						<td class='num'>");
                    Add(GetPSOStringValue(policy, "ScreenSaveTimeOut"));
                    Add(@"</td>
						<td class='text'>");
                    Add(GetPSOStringValue(policy, "ScreenSaverGracePeriod"));
                    Add(@"</td>
					</tr>
");
				}
			}
			Add(@"
				</tbody>
			</table>
		</div>
");
		}

		#endregion password policies

		#region GPO
		protected void GenerateGPODetail()
		{
			GenerateSubSection("Obfuscated Passwords", "gpoobfuscatedpassword");
            Add(@"
		<div class=""row col-lg-12"">
			<p>The password in GPO are obfuscated, not encrypted. Consider any passwords listed here as compromissed and change it immediatly.</p>
		</div>
");
			if (Report.GPPPassword != null && Report.GPPPassword.Count > 0)
			{
				Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>GPO Name</th>
					<th>Password origin</th>
					<th>UserName</th>
					<th>Password</th>
					<th>Changed</th>
					<th>Other</th>
				</tr>
				</thead>
				<tbody>
");
				foreach (GPPPassword password in Report.GPPPassword)
				{
                    Add(@"
					<tr>
						<td class='text'>");
                    AddEncoded(password.GPOName);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(password.Type);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(password.UserName);
                    Add(@"</td>
						<td class='text'><span class=""unticked"">");
                    AddEncoded(password.Password);
                    Add(@"</span></td>
						<td class='text'>");
                    Add(password.Changed);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(password.Other);
                    Add(@"</td>
					</tr>
    ");
				}
				Add(@"
				</tbody>
			</table>
		</div>
");
			}

            GenerateSubSection("Restricted Groups");
            Add(@"
		<div class=""row col-lg-12"">
			<p>Giving local group membership in a GPO is a way to become administrator.<br>
			The local admin of a domain controller can become domain administrator instantly.</p>
		</div>
");
			if (Report.GPOLocalMembership != null && Report.GPOLocalMembership.Count > 0)
			{
				Report.GPOLocalMembership.Sort((GPOMembership a, GPOMembership b) =>
				{
					int sort = String.Compare(a.GPOName, b.GPOName);
					if (sort == 0)
						sort = String.Compare(a.User, b.User);
					if (sort == 0)
						sort = String.Compare(a.MemberOf, b.MemberOf);
					return sort;
				}
				);
				Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>GPO Name</th>
					<th>User or group</th>
					<th>Member of</th>
					</tr>
				</thead>
				<tbody>");

				foreach (GPOMembership membership in Report.GPOLocalMembership)
				{
                    Add(@"
					<tr>
						<td class='text'>");
                    AddEncoded(membership.GPOName);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(membership.User);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(membership.MemberOf);
                    Add(@"</td>
					</tr>
");
				}
				Add(@"
				</tbody>
			</table>
		</div>
");
			}

			GenerateSubSection("Security settings", "lsasettings");
			Add(@"
		<div class=""row col-lg-12"">
			<p>A GPO can be used to deploy security settings to workstations.<br>The best practice out of the default security baseline in reported in <span class=""ticked"">green</span>.<br>The following settings in <span class=""unticked"">red</span> are unsual and may need to be reviewed.<br>Each setting is accompagnied which its value and a link to the GPO explanation.</p>
		</div>
");
			Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>Policy Name</th>
					<th>Setting</th>
					<th>Value</th></tr>
				</thead>
				<tbody>");
			if (Report.GPPPasswordPolicy != null)
			{
				foreach (GPPSecurityPolicy policy in Report.GPOLsaPolicy)
				{
					foreach (GPPSecurityPolicyProperty property in policy.Properties)
					{
						Add(@"
					<tr>
						<td class='text'>");
						AddEncoded(policy.GPOName);
						Add(@"</td>
						<td class='text'>");
						Add(GetLinkForLsaSetting(property.Property));
						Add(@"</td>
						<td class='num'>");
						Add(GetLsaSettingsValue(property.Property, property.Value));
						Add(@"</td>
					</tr>
");
					}
				}
			}
			Add(@"
				</tbody>
			</table>
		</div>
");

			GenerateSubSection("Privileges", "gpoprivileges");
            Add(@"
		<div class=""row col-lg-12"">
			<p>Giving privileges in a GPO is a way to become administrator without being part of a group.<br>
			For example, SeTcbPriviledge give the right to act as SYSTEM, which has more privileges than the administrator account.</p>
		</div>
");
			if (Report.GPPRightAssignment != null && Report.GPPRightAssignment.Count > 0)
			{
				Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>GPO Name</th>
					<th>Privilege</th>
					<th>Members</th>
					</tr>
				</thead>
				<tbody>");

				foreach (GPPRightAssignment right in Report.GPPRightAssignment)
				{
                    Add(@"
					<tr>
						<td class='text'>");
                    AddEncoded(right.GPOName);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(right.Privilege);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(right.User);
                    Add(@"</td>
					</tr>
");
				}
				Add(@"
				</tbody>
			</table>
		</div>
");
			}
            GenerateSubSection("GPO Login script", "gpologin");
            Add(@"
		<div class=""row col-lg-12"">
			<p>A GPO login script is a way to force the execution of data on behalf of users.</p>
		</div>
");
			if (Report.GPOLoginScript != null && Report.GPOLoginScript.Count > 0)
			{
				Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>GPO Name</th>
					<th>Action</th>
					<th>Source</th>
					<th>Command line</th>
					<th>Parameters</th>
					</tr>
				</thead>
				<tbody>");

				foreach (HealthcheckGPOLoginScriptData loginscript in Report.GPOLoginScript)
				{
                    Add(@"
					<tr>
						<td class='text'>");
                    AddEncoded(loginscript.GPOName);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(loginscript.Action);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(loginscript.Source);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(loginscript.CommandLine);
                    Add(@"</td>
						<td class='text'>");
                    AddEncoded(loginscript.Parameters);
                    Add(@"</td>
					</tr>
");
				}
				Add(@"
				</tbody>
			</table>
		</div>
");
			}
			if (version >= new Version(2, 7, 0, 0))
			{
				GenerateSubSection("GPO Deployed Files", "gpodeployedfiles");
				Add(@"
		<div class=""row col-lg-12"">
			<p>A GPO can be used to deploy applications or copy files. These files may be controlled by a third party to control the execution of local programs.</p>
		</div>
");
				if (Report.GPPFileDeployed != null && Report.GPPFileDeployed.Count > 0)
				{
					Add(@"
		<div class=""row col-lg-12 table-responsive"">
			<TABLE class=""table table-striped table-bordered"">
				<thead><tr> 
					<th>GPO Name</th>
					<th>Type</th>
					<th>File</th>
					</tr>
				</thead>
				<tbody>");

					foreach (var file in Report.GPPFileDeployed)
					{
						Add(@"
					<tr>
						<td class='text'>");
						AddEncoded(file.GPOName);
						Add(@"</td>
						<td class='text'>");
						AddEncoded(file.Type);
						Add(@"</td>
						<td class='text'>");
						AddEncoded(file.FileName);
						Add(@"</td>
					</tr>
");
					}
					Add(@"
				</tbody>
			</table>
		</div>
");
				}
			}
		}
		#endregion GPO
	}
}
